package jannex.sql.extensions;

import jannex.Builder;
import jannex.Jannex;
import jannex.mapping.ResultSetHandler;
import jannex.internal.ExceptionFactory;
import jannex.query.QueryBuilder;
import jannex.sql.Query;
import jannex.internal.SqlTypeUtils;
import jannex.sql.*;

import java.sql.Connection;
import java.sql.SQLClientInfoException;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.Savepoint;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Executor;

public class ConnectionImpl implements JxConnection {

    private final Connection connection;
    private final Jannex jannex;

    public ConnectionImpl(Connection connection, Jannex jannex) {
        this.connection = connection;
        this.jannex = jannex;
    }

    private Builder<JxConnection> builder() {
        final JxConnection zombieConnection = new ConnectionImpl(connection, jannex) {
            @Override
            public void close() {
                // It just won't die!
            }
        };
        return new Builder<JxConnection>() {
           @Override
            public JxConnection build() {
                return zombieConnection;
            }
        };
    }

    @Override
    public Query query(String sql) {
        return new Query(builder(), jannex, sql);
    }

    @Override
    public <E> QueryBuilder<E> plan(Class<E> elementClass) {
        return plan(jannex.getHandler(elementClass));
    }

    @Override
    public <E> QueryBuilder<E> plan(ResultSetHandler<E> handler) {
        return new QueryBuilder<>(handler);
    }

    @Override
    public JxStatement createStatement() {
        try {
            return new StatementImpl(connection.createStatement(), jannex);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public JxPreparedStatement prepareStatement(String sql) {
        try {
            return new PreparedStatementImpl(connection.prepareStatement(sql), jannex);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public JxCallableStatement prepareCall(String sql) {
        try {
            return new CallableStatementImpl(connection.prepareCall(sql), jannex);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public <E> TypedArray<E> createArrayOf(Class<E> elementClass, E[] elements) {
        try {
            return new TypedArrayImpl<>(
                    connection.createArrayOf(SqlTypeUtils.nameOfType(elementClass), elements), jannex, elementClass);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    @SuppressWarnings("unchecked")
    public <E> TypedArray<E> createArrayOf(E[] array) {
        return this.createArrayOf((Class<E>) array.getClass().getComponentType(), array);
    }


    @Override
    public String nativeSQL(String sql) {
        try {
            return connection.nativeSQL(sql);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public void setAutoCommit(boolean autoCommit) {
        try {
            connection.setAutoCommit(autoCommit);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public boolean getAutoCommit() {
        try {
            return connection.getAutoCommit();
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public void commit() {
        try {
            connection.commit();
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public void rollback() {
        try {
            connection.rollback();
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public void close() {
        try {
            connection.close();
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public boolean isClosed() {
        try {
            return connection.isClosed();
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public JxDatabaseMetaData getMetaData() {
        try {
            return new DatabaseMetaDataImpl(connection.getMetaData(), jannex);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public void setReadOnly(boolean readOnly) {
        try {
            connection.setReadOnly(readOnly);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public boolean isReadOnly() {
        try {
            return connection.isReadOnly();
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public void setCatalog(String catalog) {
        try {
            connection.setCatalog(catalog);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public String getCatalog() {
        try {
            return connection.getCatalog();
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public void setTransactionIsolation(int level) {
        try {
            connection.setTransactionIsolation(level);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public int getTransactionIsolation() {
        try {
            return connection.getTransactionIsolation();
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public SQLWarning getWarnings() {
        try {
            return connection.getWarnings();
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public void clearWarnings() {
        try {
            connection.clearWarnings();
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public JxStatement createStatement(int resultSetType, int resultSetConcurrency) {
        try {
            return new StatementImpl(connection.createStatement(resultSetType, resultSetConcurrency), jannex);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public JxPreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) {
        try {
            return new PreparedStatementImpl(connection.prepareStatement(sql, resultSetType, resultSetConcurrency), jannex);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public JxCallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) {
        try {
            return new CallableStatementImpl(connection.prepareCall(sql, resultSetType, resultSetConcurrency), jannex);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public Map<String, Class<?>> getTypeMap() {
        try {
            return connection.getTypeMap();
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public void setTypeMap(Map<String, Class<?>> map) {
        try {
            connection.setTypeMap(map);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public void setHoldability(int holdability) {
        try {
            connection.setHoldability(holdability);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public int getHoldability() {
        try {
            return connection.getHoldability();
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public JxSavepoint setSavepoint() {
        try {
            return new SavepointImpl(connection.setSavepoint(), jannex);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public JxSavepoint setSavepoint(String name) {
        try {
            return new SavepointImpl(connection.setSavepoint(name), jannex);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public void rollback(Savepoint savepoint) {
        try {
            connection.rollback(savepoint);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public void releaseSavepoint(Savepoint savepoint) {
        try {
            connection.releaseSavepoint(savepoint);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public JxStatement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) {
        try {
            return new StatementImpl(connection.createStatement(resultSetType, resultSetConcurrency, resultSetHoldability), jannex);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public JxPreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) {
        try {
            return new PreparedStatementImpl(connection.prepareStatement(sql, resultSetType, resultSetConcurrency, resultSetHoldability), jannex);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public JxCallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) {
        try {
            return new CallableStatementImpl(connection.prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability), jannex);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public JxPreparedStatement prepareStatement(String sql, int autoGeneratedKeys) {
        try {
            return new PreparedStatementImpl(connection.prepareStatement(sql, autoGeneratedKeys), jannex);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public JxPreparedStatement prepareStatement(String sql, int[] columnIndexes) {
        try {
            return new PreparedStatementImpl(connection.prepareStatement(sql, columnIndexes), jannex);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public JxPreparedStatement prepareStatement(String sql, String[] columnNames) {
        try {
            return new PreparedStatementImpl(connection.prepareStatement(sql, columnNames), jannex);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public JxClob createClob() {
        try {
            return new ClobImpl(connection.createClob(), jannex);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public JxBlob createBlob() {
        try {
            return new BlobImpl(connection.createBlob(), jannex);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public JxNClob createNClob() {
        try {
            return new NClobImpl(connection.createNClob(), jannex);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public JxSQLXML createSQLXML() {
        try {
            return new SQLXMLImpl(connection.createSQLXML(), jannex);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public boolean isValid(int timeout) {
        try {
            return connection.isValid(timeout);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public void setClientInfo(String name, String value) {
        try {
            connection.setClientInfo(name, value);
        } catch (SQLClientInfoException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public void setClientInfo(Properties properties) {
        try {
            connection.setClientInfo(properties);
        } catch (SQLClientInfoException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public String getClientInfo(String name) {
        try {
            return connection.getClientInfo(name);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public Properties getClientInfo() {
        try {
            return connection.getClientInfo();
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public JxArray createArrayOf(String typeName, Object[] elements) {
        try {
            return new ArrayImpl(connection.createArrayOf(typeName, elements), jannex);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public JxStruct createStruct(String typeName, Object[] attributes) {
        try {
            return new StructImpl(connection.createStruct(typeName, attributes), jannex);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public void setSchema(String schema) {
        try {
            connection.setSchema(schema);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public String getSchema() {
        try {
            return connection.getSchema();
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public void abort(Executor executor) {
        try {
            connection.abort(executor);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public void setNetworkTimeout(Executor executor, int milliseconds) {
        try {
            connection.setNetworkTimeout(executor, milliseconds);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public int getNetworkTimeout() {
        try {
            return connection.getNetworkTimeout();
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    @SuppressWarnings("unchecked")
    public <T> T unwrap(Class<T> iface) {
        try {
            if (iface.equals(Connection.class)) {
                return (T) connection;
            }
            return connection.unwrap(iface);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

    @Override
    public boolean isWrapperFor(Class<?> iface) {
        try {
            return iface.equals(Connection.class) || connection.isWrapperFor(iface);
        } catch (SQLException e) {
            throw ExceptionFactory.make(e);
        }
    }

}